package org.newlucene.core.store;

import java.io.IOException;
import java.io.File;
import java.io.RandomAccessFile;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public final class FSDirectory implements Directory
{
    /** A buffer optionally used in renameTo method */
    private byte[] buffer = null;

    private File directory = null;
    
    public FSDirectory(String path, boolean create) throws IOException
    {
        this(new File(path), create);
    }

    // if directory does not exist, will create it, otherwise, just use the directory
    public FSDirectory(File path, boolean create) throws IOException
    {
        directory = path;
        if (create)
        {
            create();
        }
        
        if (!directory.isDirectory())
        {
            throw new IOException(path + " not a directory");
        }
    }

    private void create() throws IOException
    {
        if (!directory.exists())
        {
            if (!directory.mkdirs())
            {
                throw new IOException("Cannot create directory: " + directory);
            }
        }

        String[] files = directory.list(); // clear old files
        for (int i = 0; i < files.length; i++)
        {
            File file = new File(directory, files[i]);
            if (!file.delete()) throw new IOException("couldn't delete " + files[i]);
        }
    }
    
    /** Returns an array of strings, one for each file in the directory. */
    public final String[] list() throws IOException
    {
        return directory.list();
    }

    /** Returns true iff a file with the given name exists. */
    public final boolean fileExists(String name) throws IOException
    {
        File file = new File(directory, name);
        return file.exists();
    }

    /** Returns the time the named file was last modified. */
    public final long fileModified(String name) throws IOException
    {
        File file = new File(directory, name);
        return file.lastModified();
    }

    /** Returns the time the named file was last modified. */
    public static final long fileModified(File directory, String name) throws IOException
    {
        File file = new File(directory, name);
        return file.lastModified();
    }

    /** Set the modified time of an existing file to now. */
    public void touchFile(String name) throws IOException
    {
        File file = new File(directory, name);
        file.setLastModified(System.currentTimeMillis());
    }

    /** Returns the length in bytes of a file in the directory. */
    public final long fileLength(String name) throws IOException
    {
        File file = new File(directory, name);
        return file.length();
    }

    /** Removes an existing file in the directory. */
    public final void deleteFile(String name) throws IOException
    {
        File file = new File(directory, name);
        if (!file.delete()) throw new IOException("Cannot delete " + name);
    }

    /** Renames an existing file in the directory. */
    public final synchronized void renameFile(String from, String to) throws IOException
    {
        File old = new File(directory, from);
        File nu = new File(directory, to);

        /*
         * This is not atomic. If the program crashes between the call to
         * delete() and the call to renameTo() then we're screwed, but I've been
         * unable to figure out how else to do this...
         */

        if (nu.exists()) if (!nu.delete()) throw new IOException("Cannot delete " + to);

        // Rename the old file to the new one. Unfortunately, the renameTo()
        // method does not work reliably under some JVMs. Therefore, if the
        // rename fails, we manually rename by copying the old file to the new
        // one
        if (!old.renameTo(nu))
        {
            java.io.InputStream in = null;
            java.io.OutputStream out = null;
            try
            {
                in = new FileInputStream(old);
                out = new FileOutputStream(nu);
                // see if the buffer needs to be initialized. Initialization is
                // only done on-demand since many VM's will never run into the
                // renameTo
                // bug and hence shouldn't waste 1K of mem for no reason.
                if (buffer == null)
                {
                    buffer = new byte[1024];
                }
                int len;
                while ((len = in.read(buffer)) >= 0)
                {
                    out.write(buffer, 0, len);
                }

                // delete the old file.
                old.delete();
            }
            catch (IOException ioe)
            {
                throw new IOException("Cannot rename " + from + " to " + to);
            }
            finally
            {
                if (in != null)
                {
                    try
                    {
                        in.close();
                    }
                    catch (IOException e)
                    {
                        throw new RuntimeException("Cannot close input stream: " + e.getMessage());
                    }
                }
                if (out != null)
                {
                    try
                    {
                        out.close();
                    }
                    catch (IOException e)
                    {
                        throw new RuntimeException("Cannot close output stream: " + e.getMessage());
                    }
                }
            }
        }
    }

    /**
     * Creates a new, empty file in the directory with the given name. Returns a
     * stream writing this file.
     */
    public final OutputStream createFile(String name) throws IOException
    {
        return new FSOutputStream(new File(directory, name));
    }

    /** Returns a stream reading an existing file. */
    public final InputStream openFile(String name) throws IOException
    {
        return new FSInputStream(new File(directory, name));
    }

    /** Closes the store to future operations. */
    public final synchronized void close() throws IOException
    {
    }

    public File getFile()
    {
        return directory;
    }

    /** For debug output. */
    public String toString()
    {
        return "FSDirectory@" + directory;
    }
}

final class FSInputStream extends InputStream
{
    private class Descriptor extends RandomAccessFile
    {
        /* DEBUG */
        // private String name;
        /* DEBUG */
        public long position;

        public Descriptor(File file, String mode) throws IOException
        {
            super(file, mode);
            /* DEBUG */
            // name = file.toString();
            // debug_printInfo("OPEN");
            /* DEBUG */
        }

        /* DEBUG */
        // public void close() throws IOException {
        // debug_printInfo("CLOSE");
        // super.close();
        // }
        //
        // private void debug_printInfo(String op) {
        // try { throw new Exception(op + " <" + name + ">");
        // } catch (Exception e) {
        // java.io.StringWriter sw = new java.io.StringWriter();
        // java.io.PrintWriter pw = new java.io.PrintWriter(sw);
        // e.printStackTrace(pw);
        // System.out.println(sw.getBuffer().toString());
        // }
        // }
        /* DEBUG */
    }

    Descriptor file = null;

    boolean isClone;

    public FSInputStream(File path) throws IOException
    {
        file = new Descriptor(path, "r");
        length = file.length();
    }

    /** InputStream methods */
    protected final void readInternal(byte[] b, int offset, int len) throws IOException
    {
        synchronized (file)
        {
            long position = getFilePointer();
            if (position != file.position)
            {
                file.seek(position);
                file.position = position;
            }
            int total = 0;
            do
            {
                int i = file.read(b, offset + total, len - total);
                if (i == -1) throw new IOException("read past EOF");
                file.position += i;
                total += i;
            }
            while (total < len);
        }
    }

    public final void close() throws IOException
    {
        if (!isClone) file.close();
    }

    /** Random-access methods */
    protected final void seekInternal(long position) throws IOException
    {
    }

    protected final void finalize() throws IOException
    {
        close(); // close the file
    }

    public Object clone()
    {
        FSInputStream clone = (FSInputStream) super.clone();
        clone.isClone = true;
        return clone;
    }

    /**
     * Method used for testing. Returns true if the underlying file descriptor
     * is valid.
     */
    boolean isFDValid() throws IOException
    {
        return file.getFD().valid();
    }
}

final class FSOutputStream extends OutputStream
{
    RandomAccessFile file = null;

    public FSOutputStream(File path) throws IOException
    {
        file = new RandomAccessFile(path, "rw");
    }

    /** output methods: */
    public final void flushBuffer(byte[] b, int size) throws IOException
    {
        file.write(b, 0, size);
    }

    public final void close() throws IOException
    {
        super.close();
        file.close();
    }

    /** Random-access methods */
    public final void seek(long pos) throws IOException
    {
        super.seek(pos);
        file.seek(pos);
    }

    public final long length() throws IOException
    {
        return file.length();
    }

    protected final void finalize() throws IOException
    {
        file.close(); // close the file
    }
}